package com.db4o.odbgen.plugins.java.maingenerator.typedxmlschema;

import java.util.ArrayList;
import java.util.List;

import org.apache.velocity.Template;
import org.apache.velocity.VelocityContext;

import com.db4o.internal.odbgen.StringUtils;
import com.db4o.internal.odbgen.TemplatesUtils;
import com.db4o.internal.odbgen.plugins.TemplateList;
import com.db4o.odbgen.OdbgenException;
import com.db4o.odbgen.plugins.xmlschema.XmlSchema;


public class TypedXmlSchema {
	
	/**
	 * The map of type declarations. The key represents the fully qualified name of the declaration.
	 */
	private TypeDeclarationCollection _typeDeclarations;
	
	private String _standardTemplatesJar;

	/**
	 * Contains the list of standard templates used by the class.
	 * @author liviug
	 */
	private enum Templates implements TemplateList{
		GETTER(Templates.path, "getter.vm"),
		SETTER(Templates.path, "setter.vm");
		private static final String path = "standardPlugins/java/maingenerator";

		
		private String _path;
		private String _name;

		Templates(String path, String name){
			this._path = path;
			this._name = name;
		}
		
		/**
		 * Returns the path and the name of the template.
		 */
		public String getQualifiedName() {
			return this._path+"/"+this._name;
		}
	}
	
	/**
	 * Creates a typed xml schema given the XmlSchema.
	 * @param xmlSchema The XmlSchema.
	 * @param rootPackage All types defined within XmlSchema will be prefixed
	 * with this.
	 * @param globalImports A comma separated list of java imports. 
	 * This list will be appended to each class, enum, interface.
	 * @param standardTemplatesJarName The name of the standard templates jar.
	 * @throws OdbgenException
	 */
	public TypedXmlSchema(
			XmlSchema xmlSchema, 
			String rootPackage,
			String globalImports,
			String standardTemplatesJar
			) throws OdbgenException{
		//First step: generate generate classes, enums, interfaces.
		TypedXmlSchemaGeneratorStep1 gen1 = new TypedXmlSchemaGeneratorStep1(xmlSchema, rootPackage);
		this._typeDeclarations = gen1.generate();
		this._standardTemplatesJar = standardTemplatesJar;
		
		//Second step: refine the generate type declarations.
		TypedXmlSchemaGeneratorStep2 gen2 = new TypedXmlSchemaGeneratorStep2(
				this,
				globalImports
				);
		gen2.generate();
	}

	/**
	 * Returns all type declarations (classes, interfaces, enums).
	 * This may be used in order to add a new type declaration.
	 */
	public TypeDeclarationCollection getTypeDeclarations(){
		return this._typeDeclarations;
	}
	
	
	/**
	 * Returns all classes.
	 * This may not be used to add a new class to the collection of type declarations.
	 */
	public List<TSClass> getClasses(){
		List<TSClass> res = new ArrayList<TSClass>();
		for(TypeDeclaration decl : this._typeDeclarations){
			if (decl instanceof TSClass) {
				TSClass c = (TSClass) decl;
				res.add(c);
			}
		}
		
		return res;
	}

	/**
	 * Returns all interfaces.
	 * This may not be used to add a new interface to the collection of type declarations.
	 */
	public List<TSInterface> getInterfaces(){
		List<TSInterface> res = new ArrayList<TSInterface>();
		for(TypeDeclaration decl : this._typeDeclarations){
			if (decl instanceof TSInterface) {
				TSInterface itf = (TSInterface) decl;
				res.add(itf);
			}
		}
		
		return res;
	}

	/**
	 * Returns all enumerations.
	 * This may not be used to add a new interface to the collection of type declarations.
	 */
	public List<TSEnum> getEnumerations(){
		List<TSEnum> res = new ArrayList<TSEnum>();
		for(TypeDeclaration decl : this._typeDeclarations){
			if (decl instanceof TSEnum) {
				TSEnum en = (TSEnum) decl;
				res.add(en);
			}
		}
		
		return res;
	}
	
	/**
	 * Receives a type name and returns one of the {@link TypeDeclaration} existing
	 * in this {@link TypedXmlSchema}.
	 * @param typeName
	 * @param typeName The name that should be resolved into a type declaration.
	 * This may be either a fully qualified name or a simple name.
	 * @return The type declaration if found. Returns null otherwise.
	 * @throws OdbgenTypeNotFoundException If the type is not found. 
	 * @throws OdbgenTypeConflictException If there is a type conflict 
	 * (multiple types match but they belong to different packages). 
	 */
	public TypeDeclaration resolveType(String typeName) throws OdbgenTypeNotFoundException, OdbgenTypeConflictException{
			return TypedXmlSchemaGeneratorStep1.resolveType(this._typeDeclarations, typeName);
	}
	
	/**
	 * Builds a getter method given it's corresponding field.
	 * @param field The field associated with the getter.
	 * @param tsClass The class to build the getter into.
	 * @param modifiers The modifiers to apply to the getter.
	 * @param methodName The name of the method to generate the getter with.
	 * @return The getter method.
	 * @throws OdbgenException
	 */
	public TSMethod buildGetterMethod(
			TSClass tsClass,
			TSField field, 
			List<TSModifier> modifiers, 
			String methodName 
			) throws OdbgenException {
		Template template = TemplatesUtils.getTemplateJar(TypedXmlSchema.Templates.GETTER.getQualifiedName(), this._standardTemplatesJar);
		VelocityContext context = new VelocityContext();
		context.put("fieldName", field.getName() );
		String gContent = TemplatesUtils.merge(template, context);
		
		TSMethod getter = new TSMethod(
				false,
				modifiers,
				field.getType(),
				methodName,
				new ArrayList<TSMethodParameter>(),
				gContent
				);
		return getter;
	}
	
	/**
	 * Builds a setter method given it's corresponding field.
	 * @param field The field associated with the setter.
	 * @param tsClass The class to build the setter into.
	 * @param modifiers The modifiers to apply to the setter.
	 * @param methodName The name of the method to generate the setter with.
	 * @return The setter method.
	 * @throws OdbgenException
	 */
	public TSMethod buildSetterMethod(
			TSClass tsClass,
			TSField field, 
			List<TSModifier> modifiers, 
			String methodName 
			) throws OdbgenException {
		Template template = TemplatesUtils.getTemplateJar(TypedXmlSchema.Templates.SETTER.getQualifiedName(), this._standardTemplatesJar);
		VelocityContext context = new VelocityContext();
		context.put("fieldName", field.getName() );
		context.put("parameterName", field.getName() );
		String sContent = TemplatesUtils.merge(template, context);
		
		List<TSMethodParameter> params = new ArrayList<TSMethodParameter>();
		params.add(new TSMethodParameter(
				field.getName(),
				field.getType()
				));
		
		TSMethod setter = new TSMethod(
				false,
				modifiers,
				new DataType("", PrimitiveType.VOID.getName(), new ArrayList<DataType>()),
				methodName,
				params,
				sContent
				);
		return setter;
	}
	
	/**
	 * Returns the name of the getter associated with the base name of a {@link TSGetterSetter}.
	 * @param fieldName
	 */
	public String buildGetterName(TSGetterSetter gs){
		StringBuilder res = new StringBuilder();
		res.append("get");
		res.append(gs.getBaseName().substring(0, 1).toUpperCase());
		res.append(gs.getBaseName().substring(1, gs.getBaseName().length()));
		return res.toString();
	}
	
	/**
	 * Returns the name of the setter associated with the base name of a {@link TSGetterSetter}.
	 * @param fieldName
	 */
	public String buildSetterName(TSGetterSetter gs){
		StringBuilder res = new StringBuilder();
		res.append("set");
		res.append(gs.getBaseName().substring(0, 1).toUpperCase());
		res.append(gs.getBaseName().substring(1, gs.getBaseName().length()));
		return res.toString();
	}
	
	/**
	 * Returns the name of the getter associated with a {@link TSField}.
	 * @param field
	 */
	public String buildGetterName(TSField field) {
		StringBuilder res = new StringBuilder();
		res.append("get");
		res.append(StringUtils.buildUppercaseName(field.getName()));
		return res.toString();
	}

	/**
	 * Returns the name of the setter associated with a {@link TSField}.
	 * @param field
	 */
	public String buildSetterName(TSField field) {
		StringBuilder res = new StringBuilder();
		res.append("set");
		res.append(StringUtils.buildUppercaseName(field.getName()));
		return res.toString();
	}

	
	@Override
	public String toString() {
		StringBuilder res = new StringBuilder();
		
		for(TSClass c : this.getClasses()){
			res.append(c.toString(0));
		}

		for(TSInterface i : this.getInterfaces()){
			res.append(i.toString(0));
		}

		for(TSEnum e : this.getEnumerations()){
			res.append(e.toString(0));
		}
		
		return res.toString();
	}

}
